import { 
  Color, 
  Geometry, 
  Group, 
  Line, 
  LineBasicMaterial, 
  Mesh, 
  MeshStandardMaterial, 
  ShaderMaterial, 
  SphereBufferGeometry, 
  Sprite, 
  SpriteMaterial, 
  Texture, 
  Vector3 
} from "three"

interface INT_Points {
  color?: number,
  size?: number,
  radius?: number,
  opacity?: number
}
interface INT_Position {
  x: number, 
  y: number, 
  z: number
}
interface INT_Line {
  /** 定义线的颜色，默认为白色，格式为 0xffffff */
  color?: number,
  /** 定义与圆的距离，默认为 0 */
  offset?: number,
  /** 定义线的透明度，默认为 1 */
  opacity?: number,
  /** 定义线的长度，默认长度为 10 */
  length?: number
}
interface INT_Text {
  /** 字体，默认为 Arial */
  fontFace?: string,
  /** 字体大小， 默认为 28 */
  fontSize?: number,
  /** 文字颜色，默认为黑色 */
  color?: string,
  /** 是否使用开发模式，方便调试，默认为 false */
  debugger?: boolean,
  /** 文字偏移 */
  offset?: {
    /** x 轴偏移，默认为 0 */
    x?: number,
    /** y 轴偏移，默认为 0 */
    y?: number
  }
}
interface INT_Ptions {
  /** 显示内容 */
  text: string,
  /** 标签定位，[x: number, y: number, z: number] */
  position: number[],
  /** 点的配制 */
  points: INT_Points,
  /** 线条配制 */
  line?: INT_Line,
  /** 字体配制 */
  font?: INT_Text
}

class AirLabel {
  group: Group
  position: INT_Position
  points: INT_Points

  /**
   * 创建浮动标签
   * @param options 标签配制
   * 
   * @example
   * const label = new AirLabel({
   *   text: '显示内容',
   *   position: [0, 0, 0],
   *   font: {
   *     fontSize: 28,
   *     fontFace: 'Arial',
   *     color: '#000000',
   *     offset: {x: 0, y: 0}
   *   },
   *   line: {
   *     offset: 0,
   *     opacity: 1,
   *     length: 10
   *   },
   *   points: {
   *     color: oxffffff,
   *     size: 0.3,
   *     radius: 1,
   *     opacity: 0.5
   *   }
   * });
   * scene.add(label);
   */
  constructor(options: INT_Ptions) {
    this.group = new Group();
    this.position = {
      x: options.position[0],
      y: options.position[1],
      z: options.position[2],
    };
    this.points = options.points;

    this.group.add( drawPoint(this.points) );
    this.group.add( drawLine(options.line) );
    this.group.add( drawText(options.text, options.font) );

    // @ts-ignore
    this.group.position.set(...Object.values(this.position));
    console.log(this.group)
  }

  show() {
    this.group.visible = true;
  }

  hide() {
    this.group.visible = false;
  }
}

function drawPoint(options: INT_Points) {
  let group = new Group();
  let size = options.size ?? 0.3;
  let geom = new SphereBufferGeometry(size, 32, 32);
  let mater = new MeshStandardMaterial({
    color: options?.color ?? 0xffffff,
  });
  let mesh = new Mesh(geom, mater);
  group.add(mesh);

  let geom2 = new SphereBufferGeometry(options.radius, 32, 32);
  let mater2 = new MeshStandardMaterial({
    transparent: true,
    opacity: options.opacity ?? 0.5,
    color: options?.color ?? 0xffffff 
  });
  let mesh2 = new Mesh(geom2, mater2);
  group.add(mesh2);
  
  return group
}

function drawLine(opts?: INT_Line) {
  const vertexShader = `
    varying vec3 vPos;
    void main() {
      vPos = position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `
  const fragmentShader = `
    uniform vec3 origin;
    uniform vec3 color;
    uniform float limitDistance;
    uniform float opacity;
    varying vec3 vPos;

    void main() {
      float distance = clamp(length(vPos - origin), 0., limitDistance);
      float opacity = (1. - distance / limitDistance) * opacity;
      gl_FragColor = vec4(color, opacity);
    }
  ` 
  const len = opts?.length ?? 10;
  const mate = new ShaderMaterial({
    vertexShader,
    fragmentShader,
    transparent: true,
    uniforms: {
      color: { value: new Color(opts?.color) },
      origin: { value: new Vector3(0, 0, 0)},
      limitDistance: { value: len },
      opacity: { value: opts?.opacity ?? 1 }
    }
  });
  const points = new Geometry();
  points.vertices.push(new Vector3(0, opts?.offset, 0));
  points.vertices.push(new Vector3(0, - len, 0));

  return new Line(points, mate);
}

function drawText(txt: string, opts?: INT_Text) {
  let fontSize = opts?.fontSize ?? 28;
  let dpr = window.devicePixelRatio;
  let { x: offsetX = 0, y: offsetY = 0 } = opts?.offset ?? {}
  let canvas = document.createElement('canvas');
  canvas.width = 300 * dpr;
  canvas.height = 150 * dpr;
  canvas.style.position = 'fixed'
  canvas.style.bottom = '0px'
  canvas.style.width = '300px';
  canvas.style.height = '150px';
  
  let context = canvas.getContext('2d') as CanvasRenderingContext2D;
  context.scale(dpr, dpr);

  context.font = `${fontSize}px ${opts?.fontFace || 'Arial'}`;

  context.fillStyle = opts?.color || 'rgba(0, 0, 0, 1)';
  context.fillText(txt, offsetX, fontSize + offsetY);

  if (opts?.debugger) {
    context.fillStyle = 'rgba(255, 0, 0, 0.1)';
    context.fillRect(0, 0, canvas.width, canvas.height);
    document.body.appendChild(canvas);
  }

  let texture = new Texture(canvas);
  texture.needsUpdate = true;

  let spriteMater = new SpriteMaterial({
    map: texture,
  })
  let sprite = new Sprite(spriteMater);
  sprite.scale.set(20, 10, 1);
  sprite.center.set(0, 0.5);
  return sprite;
}

export {
  AirLabel
}